﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace Nature.Data
{
    public partial class Thermo7
    {
        public double Cp(double t) { return Thermo7Range.Cp(Ranges.Data, t); }

        public double Cv(double t) { return Thermo7Range.Cv(Ranges.Data, t); }

        public double H(double t) { return Thermo7Range.H(Ranges.Data, t); }

        public double U(double t) { return Thermo7Range.U(Ranges.Data, t); }

        public double S(double t) { return Thermo7Range.S(Ranges.Data, t); }

        public double G(double t) { return Thermo7Range.G(Ranges.Data, t); }

        public double F(double t) { return Thermo7Range.F(Ranges.Data, t); }

    }

    public partial struct Thermo7Range : IEnumerable<double>
    {
        #region Properties

        /// <summary>
        /// Reduced heat capacity (Cp [~]) at the lowest temperature
        /// </summary>
        [XmlIgnore]
        public double Cpmin { get { return Cp(Tmin); } }

        /// <summary>
        /// Reduced heat capacity (Cp [~]) at the highest temperature
        /// </summary>
        [XmlIgnore]
        public double Cpmax { get { return Cp(Tmax); } }

        /// <summary>
        /// Reduced enthalpy (H [~]) at the lowest temperature
        /// </summary>
        [XmlIgnore]
        public double Hmin { get { return H(Tmin); } }

        /// <summary>
        /// Reduced enthalpy (H [~]) at the highest temperature
        /// </summary>
        [XmlIgnore]
        public double Hmax { get { return H(Tmax); } }

        /// <summary>
        /// Reduced entropy (S [~]) at the lowest temperature
        /// </summary>
        [XmlIgnore]
        public double Smin { get { return S(Tmin); } }

        /// <summary>
        /// Reduced entropy (S [~]) at the highest temperature
        /// </summary>
        [XmlIgnore]
        public double Smax { get { return S(Tmax); } }

        /// <summary>
        /// Sets the T/Cp/H/S reference point. 
        /// </summary>
        /// <remarks>
        /// This operation effects the A1, A6  and A7 coefficients of the polynomial fit
        /// The temperature of the reference point must belong to the temperature domain of this instance, 
        /// otherwise the ArgumentOutOfRangeException exception is thrown
        /// </remarks>
        /// <exception cref="System.ArgumentOutOfRangeException">The temperature of the reference point must belong to the temperature domain of this instance</exception>
        [XmlIgnore]
        public ThermoRef Ref
        {
            set
            {
                if (value.T < Tmin || value.T > Tmax) { throw new System.ArgumentOutOfRangeException("The temperature of the reference point must belong to the temperature domain of this instance"); }
                double t = value.T;
                A1 = (value.ReducedCp - Cp(t) + A1);
                A6 = (value.ReducedH - H(t) + A6 / t) * t;
                A7 = (value.ReducedS - S(t) + A7);
            }
        }

        /// <summary>
        /// Gets the T/Cp/H/S reference point of this instance at the lowest temperature
        /// </summary>
        [XmlIgnore]
        public ThermoRef RefLow
        {
            get
            {
                ThermoRef @ref = new ThermoRef()
                {
                    T = this.Tmin,
                    Cp = this.Cpmin * Constants.Rgas,
                    H = this.Hmin * this.Tmin * Constants.Rgas,
                    S = this.Smin * Constants.Rgas
                };
                return @ref;
            }
        }

        /// <summary>
        /// Gets the T/Cp/H/S reference point of this instance at the highest temperature
        /// </summary>
        [XmlIgnore]
        public ThermoRef RefHigh
        {
            get
            {
                ThermoRef @ref = new ThermoRef()
                {
                    T = this.Tmax,
                    Cp = this.Cpmax * Constants.Rgas,
                    H = this.Hmax * this.Tmax * Constants.Rgas,
                    S = this.Smax * Constants.Rgas
                };
                return @ref;
            }
        }
        #endregion

        #region Public Static Methods

        /// <summary>
        /// Fixes discontinuities in Cp, H or S by adjusting the 1-st, the 6-th or the 7-th coefficients in all but 1-st polynomial
        /// </summary>
        /// <param name="approx">Polynomials array</param>
        /// <exception cref="System.ArgumentException">Inconsistent temperature boundaries</exception>
        public static void FixDiscontinuities(Thermo7Range[] approx)
        {
            for (int i = 1; i < approx.Length; ++i)
            {
                Thermo7Range prev = approx[i - 1];
                Thermo7Range curr = approx[i];
                if (curr.Tmin != prev.Tmax) { throw new System.ArgumentException("Inconsistent temperature boundaries"); }
                curr.Ref = prev.RefHigh;
            }
        }

        /// <summary>
        /// Checks whether the polynomials array has any discontinuities in Cp, H or S values
        /// </summary>
        /// <param name="approx">Array of the Thermo7 objects to be tested for the discontinuities</param>
        /// <returns>true if a discontinuity has been found, otherwise false</returns>
        /// <exception cref="System.ArgumentException">Inconsistent temperature boundaries</exception>
        public static bool HasDiscontinuities(Thermo7Range[] approx, DoubleRelativeTolerance? relTol = null)
        {
            DoubleRelativeTolerance tolerance = relTol.HasValue ? relTol.Value : new DoubleRelativeTolerance(1.0e-5);
            for (int i = 1; i < approx.Length; ++i)
            {
                Thermo7Range prev = approx[i - 1];
                Thermo7Range curr = approx[i];
                if (curr.Tmin != prev.Tmax) { throw new System.ArgumentException("Inconsistent temperature boundaries"); }
                if (false == tolerance.AreSame(prev.Cpmax, curr.Cpmin)) { return true; }
                if (false == tolerance.AreSame(prev.Hmax, curr.Hmin)) { return true; }
                if (false == tolerance.AreSame(prev.Smax, curr.Smin)) { return true; }
            }
            return false;
        }

        /// <summary>
        /// Calculates the reduced heat capacity (Cp [~]) at the given temperature [K]
        /// </summary>
        /// <param name="approx">Array of the polynomial fits to be used</param>
        /// <param name="t">Temperature [K]</param>
        /// <returns>Calculated reduced heat capacity (Cp [~])</returns>
        public static double Cp(Thermo7Range[] approx, double t)
        {
            for (int i = 0; i < approx.Length; ++i)
            {
                Thermo7Range range = approx[i];
                if (t < range.Tmax) { return range.Cp(t); }
            }
            return approx[approx.Length - 1].Cp(t);
        }

        /// <summary>
        /// Calculates the reduced heat capacity (Cv [~]) at the given temperature [K]
        /// </summary>
        /// <param name="approx">Array of the polynomial fits to be used</param>
        /// <param name="t">Temperature [K]</param>
        /// <returns>Calculated reduced heat capacity (Cv [~])</returns>
        public static double Cv(Thermo7Range[] approx, double t) { return (Cp(approx, t) - 1.0); }

        /// <summary>
        /// Calculates the reduced entropy (S [~]) at the given temperature [K]
        /// </summary>
        /// <param name="approx">Array of the polynomial fits to be used</param>
        /// <param name="t">Temperature [K]</param>
        /// <returns>Reduced entropy (S [~])</returns>
        public static double S(Thermo7Range[] approx, double t)
        {
            for (int i = 0; i < approx.Length; ++i)
            {
                Debug.Assert(i == 0 ? true : approx[i - 1].Tmax == approx[i].Tmin);
                Debug.Assert(i == 0 ? true : approx[i - 1].Tmax < approx[i].Tmax);
                Thermo7Range range = approx[i];
                if (t < range.Tmax) { return range.S(t); }
            }
            return approx[approx.Length - 1].S(t);
        }

        /// <summary>
        /// Calculates the reduced enthalpy (H [~]) at the given temperature [K]
        /// </summary>
        /// <param name="approx">Array of the polynomial fits to be used</param>
        /// <param name="t">Temperature [K]</param>
        /// <returns>Reduced enthalpy (H [~])</returns>
        public static double H(Thermo7Range[] approx, double t)
        {
            for (int i = 0; i < approx.Length; ++i)
            {
                Thermo7Range range = approx[i];
                if (t < range.Tmax) { return range.H(t); }
            }
            return approx[approx.Length - 1].H(t);
        }

        /// <summary>
        /// Calculates the reduced internal energy (U [~]) at the given temperature [K]
        /// </summary>
        /// <param name="approx">Array of the polynomial fits to be used</param>
        /// <param name="t">Temperature [K]</param>
        /// <returns>Reduced internal energy (U [~])</returns>
        public static double U(Thermo7Range[] approx, double t) { return (H(approx, t) - 1.0); }

        /// <summary>
        /// Calculates the reduced Gibbs energy (G [~]) at the given temperature [K]
        /// </summary>
        /// <param name="approx">Array of the polynomial fits to be used</param>
        /// <param name="t">Temperature [K]</param>
        /// <returns>Reduced Gibbs energy (G [~])</returns>
        public static double G(Thermo7Range[] approx, double t)
        {
            return Thermo7Range.H(approx, t) - Thermo7Range.S(approx, t);
        }

        /// <summary>
        /// Calculates the reduced Helmholtz energy (F [~]) at the given temperature [K]
        /// </summary>
        /// <param name="approx">Array of the polynomial fits to be used</param>
        /// <param name="t">Temperature [K]</param>
        /// <returns>Reduced Helmholtz energy (F [~])</returns>
        public static double F(Thermo7Range[] approx, double t)
        {
            return Thermo7Range.G(approx, t) - 1.0;
        }

        #endregion
        /// <summary>
        /// Calculates the reduced isobaric heat capacity Cp [~] at given temperature T [K]
        /// </summary>
        /// <param name="t">Temperature value [K]</param>
        /// <returns>Reduced isobaric heat capacity Cp [~]</returns>
        public double Cp(double t)
        {
            if (t < Tmin) { return Cp(Tmin); }
            if (t > Tmax) { return Cp(Tmax); }
            return A1 + A2 * t + A3 * t * t + A4 * t * t * t + A5 * t * t * t * t;
        }

        /// <summary>
        /// Calculates the reduced isohoric heat capacity Cv [~] at given temperature T [K]
        /// </summary>
        /// <param name="t">Temperature T [K]</param>
        /// <returns>Reducedl isohoric heat capacity Cv [~]</returns>
        public double Cv(double t) { return Cp(t) - 1.0; }


        /// <summary>
        /// Calculates the reduced enthalpy H [~] at given temperature T [K]
        /// </summary>
        /// <param name="t">Temperature T [K]</param>
        /// <returns>Reduced enthalpy H [~] </returns>
        public double H(double t)
        {
            if (t < Tmin) { return (H(Tmin) * Tmin + Cp(Tmin) * (t - Tmin)) / t; }
            if (t > Tmax) { return (H(Tmax) * Tmax + Cp(Tmax) * (t - Tmax)) / t; }
            return A1 + A2 / 2.0 * t + A3 / 3.0 * t * t + A4 / 4.0 * t * t * t + A5 / 5.0 * t * t * t * t + A6 / t;
        }


        /// <summary>
        /// Calculates the reduced internal energy U [~] at given temperature T [K]
        /// </summary>
        /// <param name="t">Temperature T [K]</param>
        /// <returns>Reduced internal energy (U [~])</returns>
        public double U(double t) { return (H(t) - 1.0); }

        /// <summary>
        /// Calculates the reduced entropy (S [~]) at given temperature T [K]
        /// </summary>
        /// <param name="t">Temperature T [K]</param>
        /// <returns>Reduced entropy (S [~])</returns>
        public double S(double t)
        {
            if (t < Tmin) { return S(Tmin) + Cp(Tmin) * Math.Log(t / Tmin); }
            if (t > Tmax) { return S(Tmax) + Cp(Tmax) * Math.Log(t / Tmax); }
            return A1 * Math.Log(t) + A2 * t + A3 / 2.0 * t * t + A4 / 3.0 * t * t * t + A5 / 4.0 * t * t * t * t + A7;
        }



        /// <summary>
        /// Calculates the reduced Gibbs energy (G [~]) at given temperature T [K]
        /// </summary>
        /// <param name="t">Temperature T [K]</param>
        /// <returns>Reduced Gibbs energy (G [~])</returns>
        public double G(double t)
        {
            return (H(t) - S(t));
        }

        /// <summary>
        /// Calculates the reduced Helmholtz energy (F [~]) at given temperature T [K]
        /// </summary>
        /// <param name="t">Temperature T [K]</param>
        /// <returns>Reduced Helmholtz energy (F [~])</returns>
        public double F(double t)
        {
            return (U(t) - S(t));
        }

        public bool CoversTemperature(double t)
        {
            return (t >= Tmin && t <= Tmax);
        }

        #region IEnumerable<double> Members

        IEnumerator<double> IEnumerable<double>.GetEnumerator()
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            yield return A1;
            yield return A2;
            yield return A3;
            yield return A4;
            yield return A5;
            yield return A6;
            yield return A7;
        }

        #endregion
    }


    public class Thermo7IdealGasSpeciesThermoPotentials : IdealGasSpeciesThermoPotentialsBase
    {
        Thermo7[] m_thermo7;

        public Thermo7IdealGasSpeciesThermoPotentials(IEnumerable<Thermo7> thermo7, IEnumerable<double> speciesMolarMasses) : base(speciesMolarMasses) 
        {
            m_thermo7 = thermo7.ToArray();
            if (m_thermo7.Length != base.SpeciesMolarMasses.Length) 
            {
                throw new ArgumentException();
            }
        }

        public override void Cp(double temperature, double[] cp)
        {
            for (int isp = 0; isp < Nsp; ++isp)
            {
                cp[isp] = m_thermo7[isp].Cp(temperature);
            }
        }

        public override void Cv(double temperature, double[] cv)
        {
            for (int isp = 0; isp < Nsp; ++isp)
            {
                cv[isp] = m_thermo7[isp].Cv(temperature);
            }
        }

        public override void S(double temperature, double[] s)
        {
            for (int isp = 0; isp < Nsp; ++isp)
            {
                s[isp] = m_thermo7[isp].S(temperature);
            }
        }

        public override void H(double temperature, double[] h)
        {
            for (int isp = 0; isp < Nsp; ++isp)
            {
                h[isp] = m_thermo7[isp].H(temperature);
            }
        }

        public override void U(double temperature, double[] u)
        {
            for (int isp = 0; isp < Nsp; ++isp)
            {
                u[isp] = m_thermo7[isp].U(temperature);
            }
        }

        public override void G(double temperature, double[] g)
        {
            for (int isp = 0; isp < Nsp; ++isp)
            {
                g[isp] = m_thermo7[isp].G(temperature);
            }
        }

        public override void F(double temperature, double[] f)
        {
            for (int isp = 0; isp < Nsp; ++isp)
            {
                f[isp] = m_thermo7[isp].F(temperature);
            }
        }
    }

}
